/*
 *             Copyright (C) 2024 Steve Jrong
 *
 * 	   GitHub Homepage: https://www.github.com/SteveJrong
 *      Gitee Homepage: https://gitee.com/stevejrong1024
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.stevejrong.waiter.common.component.util;

import java.util.Iterator;
import java.util.Map;

import org.apache.commons.collections4.MapUtils;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.BodyInserters.FormInserter;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.RequestBodyUriSpec;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
import org.springframework.web.util.UriComponentsBuilder;

import com.stevejrong.waiter.common.component.BaseConstants;

import lombok.NoArgsConstructor;
import reactor.core.publisher.Mono;

/**
 * 工具类 - HTTP请求/响应
 *
 * @author Steve Jrong
 * @since 1.0
 */
@NoArgsConstructor
public final class HttpUtil {

    /**
     * 获取WebClient实例
     *
     * @return WebClient实例
     */
    private static WebClient getWebClientInstance() {
        return WebClient.builder()
				.defaultHeader(BaseConstants.HttpHeaderConstants.CONTENT_TYPE.desc(), BaseConstants.HttpMimeTypeConstants.JSON.desc())
				.build();
    }
    
    /**
     * 构建URL
     * 
     * @param requestUrl 请求的目标URL。不能为空。
     * @param requestParams 在请求的URL路径上，携带的查询参数集合。可为空，则不向URL地址中添加任何查询参数。
     * @return URL构建器对象UriComponentsBuilder
     */
    private static UriComponentsBuilder buildUrl(String requestUrl, Map<String, String> requestParams) {
    	UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(requestUrl);
    	
		for (String key : MapUtils.emptyIfNull(requestParams).keySet()) {
			uriComponentsBuilder.queryParam(key, requestParams.get(key));
		}
		
		return uriComponentsBuilder;
	}
	
	/**
	 * 构建请求头和请求体
	 * 
	 * @param headerParams 在请求中，携带的请求头参数集合
	 * @param bodyParams 在请求体中，携带的查询参数集合
	 * @param uriComponentsBuilder URL构建器对象
	 * @param httpMethod HTTP请求方式枚举
	 * @return 含有请求头和请求体的对象RequestBodyUriSpec
	 */
	private static RequestHeadersSpec<?> buildHeaderParamsAndBodyParams(Map<String, String> headerParams, Map<String, String> bodyParams, UriComponentsBuilder uriComponentsBuilder, HttpMethod httpMethod) {
		RequestBodyUriSpec requestBodyUriSpec = getWebClientInstance().method(httpMethod);
		requestBodyUriSpec.uri(uriComponentsBuilder.encode().build().toUri());
		
		// 构建并将数据塞入请求头
		for (String key : MapUtils.emptyIfNull(headerParams).keySet()) {
			requestBodyUriSpec.header(key, headerParams.get(key));
		}
		
		// 构建并将数据塞入请求体
		if (MapUtils.isNotEmpty(bodyParams)) {
			requestBodyUriSpec.body(generateFormDataParams(bodyParams));
		}
		
		return requestBodyUriSpec;
	}
	
	/**
     * 生成请求体数据对象
     * 
     * @param bodyParams 在请求体中，携带的查询参数集合
     * @return 请求体数据对象FormInserter
     */
	private static FormInserter<String> generateFormDataParams(Map<String, String> bodyParams) {
		FormInserter<String> formInserter = null;
		int i = 0;
		Iterator<String> iterator = bodyParams.keySet().iterator();
		while (iterator.hasNext()) {
			String key = iterator.next();
			String value = bodyParams.get(key);

			if (i <= 0) {
				formInserter = BodyInserters.fromFormData(key, value);
				i++;
				continue;
			}
			formInserter.with(key, value);
			i++;
		}

		return formInserter;
	}

    /**
     * 带URL参数发起Get请求
     * 
     * @param <T> 反序列化响应结果的类型
     * @param requestUrl Get请求的目标URL
     * @param requestParams 在Get请求的URL路径上，携带的查询参数集合
     * @param serializableClass 反序列化响应结果的类型
     * @return Mono结果流
     */
    public static <T> Mono<T> get(String requestUrl, Map<String, String> requestParams, Class<T> serializableClass) {
		return getWebClientInstance()
				.get()
				.uri(buildUrl(requestUrl, requestParams).encode().build().toUri())
				.retrieve()
				.bodyToMono(serializableClass);
    }

    /**
     * 带URL参数和请求头发起Get请求
     * 
     * @param <T> 反序列化响应结果的类型
     * @param requestUrl Get请求的目标URL
     * @param headerParams 在Get请求中，携带的请求头参数集合
     * @param requestParams 在Get请求的URL路径上，携带的查询参数集合
     * @param serializableClass 反序列化响应结果的类型
     * @return Mono结果流
     */
    public static <T> Mono<T> get(String requestUrl, Map<String, String> headerParams, Map<String, String> requestParams, Class<T> serializableClass) {
		return buildHeaderParamsAndBodyParams(headerParams, null, buildUrl(requestUrl, requestParams), HttpMethod.GET)
				.retrieve()
				.bodyToMono(serializableClass);
    }

    /**
     * 带URL参数发起Post请求
     * 
     * @param <T> 反序列化响应结果的类型
     * @param requestUrl Post请求的目标URL
     * @param bodyParams 在Post请求体中，携带的查询参数集合
     * @param serializableClass 反序列化响应结果的类型
     * @return Mono结果流
     */
    public static <T> Mono<T> post(String requestUrl, Map<String, String> bodyParams, Class<T> serializableClass) {
    	return getWebClientInstance()
				.post()
				.uri(requestUrl)
				.body(generateFormDataParams(bodyParams))
				.retrieve()
				.bodyToMono(serializableClass);
    }

    /**
     * 带URL参数和请求头发起Post请求
     * 
     * @param <T> 反序列化响应结果的类型
     * @param requestUrl Post请求的目标URL
     * @param headerParams 在Post请求中，携带的请求头参数集合
     * @param bodyParams 在Post请求体中，携带的查询参数集合
     * @param serializableClass 反序列化响应结果的类型
     * @return Mono结果流
     */
    public static <T> Mono<T> post(String requestUrl, Map<String, String> headerParams, Map<String, String> bodyParams, Class<T> serializableClass) {
		return buildHeaderParamsAndBodyParams(headerParams, bodyParams, buildUrl(requestUrl, null), HttpMethod.POST)
				.retrieve()
				.bodyToMono(serializableClass);
    }
}